package cpu

import (
	"fmt"
	"strings"

	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/instructions"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/instructions/base"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/instructions/math"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/rtda"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/rtda/heap"
	"hundsun.com/hsl/hschain/contract/jvm/jvm.go/vm"
	"hundsun.com/hsl/hschain/protos/ledger"
)

func ExecMethod(thread *rtda.Thread, method *heap.Method, args []heap.Slot) heap.Slot {
	shimFrame := rtda.NewShimFrame(thread, args)
	thread.PushFrame(shimFrame)
	thread.InvokeMethod(method)

	debug := thread.VMOptions.XDebugInstr
	defer _catchErr(thread) // todo

	for {
		frame := thread.CurrentFrame()
		if frame == shimFrame {
			thread.PopFrame()
			if frame.IsStackEmpty() {
				return heap.EmptySlot
			} else {
				return frame.Pop()
			}
		}

		pc := frame.NextPC
		thread.PC = pc

		// fetch instruction
		instr, nextPC := fetchInstruction(frame.Method, pc)
		frame.NextPC = nextPC

		// execute instruction
		instr.Execute(frame)
		if debug {
			_logInstruction(frame, instr)
		}
	}
}

func Loop(thread *rtda.Thread) {
	threadObj := thread.JThread()
	isDaemon := threadObj != nil && threadObj.GetFieldValue("daemon", "Z").IntValue() == 1
	if !isDaemon {
		//nonDaemonThreadStart()
		thread.CondLock.Lock()
		thread.AliveCount++
		thread.CondLock.Unlock()
	}

	//st := time.Now().UnixNano()
	_loop(thread)
	//et := time.Now().UnixNano()
	//fmt.Println("main thread loop last ",(et-st)/1e6)

	// terminate thread
	threadObj = thread.JThread()
	if threadObj != nil {
		threadObj.Monitor.NotifyAll()
	}

	if !isDaemon {
		//nonDaemonThreadStop()
		thread.CondLock.Lock()
		thread.AliveCount--
		if thread.AliveCount == 0 {
			thread.Cond.Broadcast()
		}
		thread.CondLock.Unlock()
	}
}

func _loop(thread *rtda.Thread) {
	debug := thread.VMOptions.XDebugInstr
	defer _catchErr(thread) // todo
	instructNum := 0
	defer func() {
		thread.Log.Info("instruction*** num is ", instructNum, " gas left ", thread.GasLeft.Load())
	}()
	for {
		frame := thread.CurrentFrame()
		pc := frame.NextPC
		thread.PC = pc
		instr, nextPC := fetchInstruction(frame.Method, pc)
		frame.NextPC = nextPC

		if debug {
			_logInstruction(frame, instr)
		}
		// TODO 目前先做指令测试后期可以放开
		if frame.Method != nil && frame.Method.Class != nil &&
			!strings.Contains(strings.ToLower(frame.Method.Class.Name), "map") &&
			!strings.Contains(strings.ToLower(frame.Method.Class.Name), "set") &&
			!strings.Contains(strings.ToLower(frame.Method.Class.Name), "hashtable") {

			//当前gas简单处理，一个指令消耗一个单位gas
			instructNum++
			thread.GasLeft.Dec()
			if thread.GasLeft.Load() <= 0 {
				frame.Thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_OUT_OF_GAS
				thread.Log.Warnf("jvm execute out of gas, current gasLeft %d.", thread.GasLeft.Load())
				panic("out of gas limit")
			}
		}

		instr.Execute(frame)
		if thread.IsStackEmpty() {
			break
		}
	}
}

func fetchInstruction(method *heap.Method, pc int) (base.Instruction, int) {
	if method.Instructions == nil {
		method.Instructions = instructions.Decode(method.Code)
	}

	instrs := method.Instructions.([]base.Instruction)
	instr := instrs[pc]

	// calc nextPC
	pc++
	for pc < len(instrs) && instrs[pc] == nil {
		pc++
	}

	return instr, pc
}

// todo
func _catchErr(thread *rtda.Thread) {
	if r := recover(); r != nil {
		if err, ok := r.(vm.ClassNotFoundError); ok {
			thread.ThrowClassNotFoundException(err.Error())
			_loop(thread)
			return
		}

		_logFrames(thread)

		err, ok := r.(error)
		if !ok {
			err = fmt.Errorf("%v", r)
			//panic(err.Error())
		}
		//panic(err.Error())
		thread.Log.Infof("Jvm executed failed ,err is :%s", err.Error())
		if thread.GroupedTx.TxExecRst.Receipt.TxStatus == ledger.TransactionReceipt_SUCCESSFUL {
			thread.GroupedTx.TxExecRst.Receipt.TxStatus = ledger.TransactionReceipt_JAVA_OTHER_EXCEPTION
		}
		threadObj := thread.JThread()
		isDaemon := threadObj != nil && threadObj.GetFieldValue("daemon", "Z").IntValue() == 1
		if !isDaemon {
			//nonDaemonThreadStop()
			thread.CondLock.Lock()
			thread.AliveCount--
			if thread.AliveCount == 0 {
				thread.Cond.Broadcast()
			}
			thread.CondLock.Unlock()
		}

		// 异常退出时，需要清理本地缓存以及kvs
		thread.ExecCtx.GetStateHandle().ClearLocal()
		thread.GroupedTx.TxExecRst.Kvs = nil
	}
}

func _logFrames(thread *rtda.Thread) {
	for !thread.IsStackEmpty() {
		frame := thread.PopFrame()
		method := frame.Method
		className := method.Class.Name
		lineNum := method.GetLineNumber(frame.NextPC)
		fmt.Printf(">> line:%4d pc:%4d %v.%v%v \n",
			lineNum, frame.NextPC, className, method.Name, method.Descriptor)
	}
}

func _logInstruction(frame *rtda.Frame, instr base.Instruction) {
	thread := frame.Thread
	method := frame.Method
	className := method.Class.Name
	pc := thread.PC

	if method.IsStatic() {
		fmt.Printf("[instruction] thread:%p %v.%v() #%v %T %v\n",
			thread, className, method.Name, pc, instr, instr)
	} else {
		fmt.Printf("[instruction] thread:%p %v#%v() #%v %T %v\n",
			thread, className, method.Name, pc, instr, instr)
	}
}

func bCalculatePC(instruction base.Instruction) bool {
	var bCalculate bool
	if _, bCalculate = instruction.(*math.IOp); bCalculate {
		return true
	} else if _, bCalculate = instruction.(*math.DOp); bCalculate {
		return true
	} else if _, bCalculate = instruction.(*math.FOp); bCalculate {
		return true
	} else if _, bCalculate = instruction.(*math.LOp); bCalculate {
		return true
	}

	return false
}
